import { Callout } from "nextra/components";
import { PackageName, NpmInstall, ImportStatement } from "../components";

# Web Workers

> Run AI tasks in background threads to keep your UI responsive.

### Quick Setup


#### Create React App

#### Option A: Create a new app manually

Create a new React app and install the required dependencies:

```bash
npx create-react-app my-geoai-app --template typescript
cd my-geoai-app
npm install maplibre-gl maplibre-gl-draw geoais
```

#### Option B: Clone an Quickstart example
```bash
git init
touch README.md
git add .
git commit -m "Initial commit"
git subtree add --prefix=examples/02-quickstart-with-workers https://github.com/decision-labs/geoai.js main --squash
```

#### Create Worker File `worker.ts`

```typescript
import { geoai } from "geoai";

let modelInstance: any = null;

self.onmessage = async e => {
  const { type, payload } = e.data;

  try {
    switch (type) {
      case "init":
        modelInstance = await geoai.pipeline(
          payload.tasks,
          payload.providerParams
        );
        self.postMessage({ type: "ready" });
        break;

      case "inference":
        const result = await modelInstance.inference(payload);
        self.postMessage({ type: "result", payload: result });
        break;
    }
  } catch (error) {
    self.postMessage({ type: "error", payload: error.message });
  }
};
```

#### Create Hook `useGeoAIWorker.ts`

```typescript
import { useState, useEffect, useRef } from "react";

export function useGeoAIWorker() {
  const workerRef = useRef<Worker | null>(null);
  const [isReady, setIsReady] = useState(false);
  const [isProcessing, setIsProcessing] = useState(false);
  const [result, setResult] = useState(null);

  useEffect(() => {
    workerRef.current = new Worker(new URL("./worker.ts", import.meta.url));

    workerRef.current.onmessage = e => {
      const { type, payload } = e.data;

      switch (type) {
        case "ready":
          setIsReady(true);
          break;
        case "result":
          setResult(payload);
          setIsProcessing(false);
          break;
        case "error":
          console.error("Worker error:", payload);
          setIsProcessing(false);
          break;
      }
    };

    return () => workerRef.current?.terminate();
  }, []);

  const initialize = (tasks: any[], providerParams: any) => {
    workerRef.current?.postMessage({
      type: "init",
      payload: { tasks, providerParams },
    });
  };

  const runInference = (params: any) => {
    if (!isReady) return;
    setIsProcessing(true);
    workerRef.current?.postMessage({
      type: "inference",
      payload: params,
    });
  };

  return { isReady, isProcessing, result, initialize, runInference };
}
```

#### Use in Component

```tsx
import React, { useEffect, useRef, useState } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import MaplibreDraw from 'maplibre-gl-draw';
import 'maplibre-gl-draw/dist/mapbox-gl-draw.css';
import { useGeoAIWorker } from './useGeoAIWorker';

const config = {
  provider: "esri",
  serviceUrl: "https://server.arcgisonline.com/ArcGIS/rest/services",
  serviceName: "World_Imagery",
  tileSize: 256,
  attribution: "ESRI World Imagery",
};

function App() {
  const mapContainer = useRef<HTMLDivElement>(null);
  const map = useRef<maplibregl.Map | null>(null);
  const [detections, setDetections] = useState<any[]>([]);
  const { isReady, result, initialize, runInference } = useGeoAIWorker();

  useEffect(() => {
    initialize([{ task: "building-detection" }], config);
  }, []);

  useEffect(() => {
    if (!mapContainer.current) return;

    map.current = new maplibregl.Map({
      container: mapContainer.current,
      style: {
        version: 8,
        sources: {
          satellite: {
            type: 'raster',
            tiles: [
              "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
            ],
            tileSize: 256,
          },
        },
        layers: [{ id: 'satellite', type: 'raster', source: 'satellite' }],
      },
      center: [54.690310447932006, 24.75763471820723],
      zoom: 15,
    });

    const draw = new MaplibreDraw({
      displayControlsDefault: false,
      controls: { polygon: true, trash: true },
    });

    // @ts-ignore
    map.current.addControl(draw);

    map.current.on('draw.create', (e) => {
      const polygon = e.features[0];
      if (!isReady) return;

      runInference({
        inputs: { polygon },
        mapSourceParams: { zoomLevel: map.current?.getZoom() || 15 },
      });
    });
  }, [isReady]);

  useEffect(() => {
    if (!result) return;

    const features = result.detections?.features || [];
    setDetections(features);

    if (map.current?.getSource('detections')) {
      map.current.removeLayer('detections');
      map.current.removeSource('detections');
    }

    map.current?.addSource('detections', { type: 'geojson', data: result.detections });
    map.current?.addLayer({
      id: 'detections',
      type: 'fill',
      source: 'detections',
      paint: { 'fill-color': '#ff0000', 'fill-opacity': 0.5 },
    });
  }, [result]);

  return (
     <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>
      <div ref={mapContainer} style={{ height: '70%', width: '100%' }} />
      <div style={{ padding: '20px', height: '30%', backgroundColor: '#f5f5f5' }}>
        <h2>Building Detection</h2>
        <p>Draw a polygon to detect buildings</p>
        {detections.length > 0 && <p>Found {detections.length} buildings!</p>}
      </div>
    </div>
  );
}

export default App;

```

### Benefits

- Non-blocking UI during AI processing
- Better user experience
- Parallel task execution
